﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using DiskInspector.AttributeLayout;
using DiskInspector.Model;
using DiskInspector.Model.FileRecordStructure;

namespace DiskInspector
{
    public partial class MainFileTableForm : Form
    {
        private readonly long sectorIndex;

        private readonly int fileRecordSize;

        private readonly byte sectorsPerCluster;

        private readonly int sectorsPrecedingPartition;

        private byte[] buffer;

        private FileRecord fileRecord;

        private List<FileRecord> fileRecords;

        private string lastSearchFileName;

        private IEnumerable<FileRecord> lastSearchResults;

        private int currentResultIndex;

        public MainFileTableForm(Form owner, long sectorIndex, int fileRecordSize, byte sectorsPerCluster, int sectorsPrecedingPartition)
        {
            this.Owner = owner;
            this.sectorIndex = sectorIndex;
            this.fileRecordSize = fileRecordSize;
            this.sectorsPerCluster = sectorsPerCluster;
            this.sectorsPrecedingPartition = sectorsPrecedingPartition;
            InitializeComponent();
        }

        private void MainFileTableForm_Load(object sender, EventArgs e)
        {
            Clear();

            using (var disk = DiskManager.GetStream())
            {
                long offset = sectorIndex * DiskManager.SectorSize + Convert.ToInt32(nud_FileRecordIndex.Value) * fileRecordSize;
                buffer = DiskManager.ReadSector(disk, offset, 2);
            }

            if (!buffer.All(b => b == 0))
            {
                fileRecord = FileRecordParser.Parse(buffer);
                LoadFileRecord(fileRecord);
            }

            PopulateFileRecordRawData(buffer);
        }

        private void LoadFileRecord(FileRecord fileRecord)
        {
            var fileRecordHead = fileRecord.FileRecordHead;
            var attributeContents = fileRecord.Attributes.Select(a => a.AttributeContent).ToList(); 

            tb_Signature.Text = new string(fileRecordHead.Signature);
            tb_OffsetToUpdateSequence.Text = fileRecordHead.OffsetToUpdateSequence.ToString();
            tb_UpdateSequenceSizeInWords.Text = fileRecordHead.UpdateSequenceSizeInWords.ToString();
            tb_LogFileSequenceNumber.Text = fileRecordHead.LogFileSequenceNumber.ToString();
            tb_SequenceNumber.Text = fileRecordHead.SequenceNumber.ToString();
            tb_HardLinkCount.Text = fileRecordHead.HardLinkCount.ToString();
            tb_OffsetToTheFirstAttribute.Text = fileRecordHead.OffsetToTheFirstAttribute.ToString();
            tb_Flags.Text = fileRecordHead.Flags.ToString();
            tb_LogicalSizeOfThisRecord.Text = fileRecordHead.LogicalSizeOfThisRecord.ToString();
            tb_PhysicalSizeOfThisRecord.Text = fileRecordHead.PhysicalSizeOfThisRecord.ToString();
            tb_FileReferenceToTheBaseRecord.Text = fileRecordHead.FileReferenceToTheBaseRecord.ToString();
            tb_NextAttributeID.Text = fileRecordHead.NextAttributeID.ToString();
            tb_Boundary.Text = fileRecordHead.Boundary.ToByteString();
            tb_FileRecordID.Text = fileRecordHead.FileRecordID.ToString();
            tb_UpdateSequenceNumber.Text = fileRecordHead.UpdateSequenceNumber.ToString();
            tb_UpdateSequenceArray.Text = fileRecordHead.UpdateSequenceArray.ToByteString();
        
            Populate(attributeContents);
        }

        private void Clear()
        {
            this.Controls.OfType<TextBox>().ToList().ForEach(ctrl => ctrl.Text = String.Empty);
            tc_Attributes.TabPages.Clear();
        }

        private void Populate(ICollection<AttributeContent> attributeContents)
        {
            attributeContents.OfType<StandardInformationAttributeContent>().ToList().ForEach(Populate);
            attributeContents.OfType<FileNameAttributeContent>().ToList().ForEach(Populate);
            attributeContents.OfType<ResidentDataAttributeContent>().ToList().ForEach(Populate);
            attributeContents.OfType<NonResidentDataAttributeContent>().ToList().ForEach(Populate);
        }

        private void Populate(StandardInformationAttributeContent standardInformationAttributeContent)
        {
            var standardInformationLayout = new StandardInformationLayout();
            standardInformationLayout.tb_FileCreationTime1.Text = DateTime.FromFileTime(standardInformationAttributeContent.FileCreationTime).ToString("F");
            standardInformationLayout.tb_FileAlteredTime.Text = DateTime.FromFileTime(standardInformationAttributeContent.FileAlteredTime).ToString("F");
            standardInformationLayout.tb_MFTChangedTime.Text = DateTime.FromFileTime(standardInformationAttributeContent.MFTChangedTime).ToString("F");
            standardInformationLayout.tb_FileReadTime.Text = DateTime.FromFileTime(standardInformationAttributeContent.FileReadTime).ToString("F");
            standardInformationLayout.tb_FilePermissions.Text = standardInformationAttributeContent.FilePermissions.ToString();
            standardInformationLayout.tb_MaximumVersionNumber.Text = standardInformationAttributeContent.MaximumVersionNumber.ToString();
            standardInformationLayout.tb_VersionNumber.Text = standardInformationAttributeContent.VersionNumber.ToString();
            standardInformationLayout.tb_ClassID.Text = standardInformationAttributeContent.ClassID.ToString();
            standardInformationLayout.tb_OwnerID.Text = standardInformationAttributeContent.OwnerID.ToString();
            standardInformationLayout.tb_SecurityID.Text = standardInformationAttributeContent.SecurityID.ToString();
            standardInformationLayout.tb_QuotaCharged.Text = standardInformationAttributeContent.QuotaCharged.ToString();
            standardInformationLayout.tb_UpdateSequenceNumber1.Text = standardInformationAttributeContent.UpdateSequenceNumber.ToString();

            var key = "StandardInformation";
            tc_Attributes.TabPages.Add(key, "Standard Information");
            tc_Attributes.TabPages[key].Controls.Add(standardInformationLayout);
        }

        private void Populate(FileNameAttributeContent fileNameAttributeContent)
        {
            var fileNameLayout = new FileNameLayout();
            fileNameLayout.tb_FileCreationTime.Text = DateTime.FromFileTime(fileNameAttributeContent.FileCreationTime).ToString("F");
            fileNameLayout.tb_FileModificationTime.Text = DateTime.FromFileTime(fileNameAttributeContent.FileModificationTime).ToString("F");
            fileNameLayout.tb_MFTModificationTime.Text = DateTime.FromFileTime(fileNameAttributeContent.MFTModificationTime).ToString("F");
            fileNameLayout.tb_FileAccessTime.Text = DateTime.FromFileTime(fileNameAttributeContent.FileAccessTime).ToString("F");
            fileNameLayout.tb_AllocatedSizeOfFile.Text = fileNameAttributeContent.AllocatedSizeOfFile.ToString();
            fileNameLayout.tb_RealSizeOfFile.Text = fileNameAttributeContent.RealSizeOfFile.ToString();

            fileNameLayout.tb_FileName.Text = fileNameAttributeContent.FileName;
            fileNameLayout.tb_FileNameNamespace.Text = fileNameAttributeContent.FileNameNamespace.ToString();

            var key = String.Format("FileName_{0}", fileNameAttributeContent.FileName);
            tc_Attributes.TabPages.Add(key, "File Name");
            tc_Attributes.TabPages[key].Controls.Add(fileNameLayout);
        }

        private void Populate(ResidentDataAttributeContent residentDataAttributeContent)
        {
            var residentDataLayout = new ResidentDataLayout();

            residentDataLayout.Data.Text = residentDataAttributeContent.FileContent.ToByteString();

            var key = "Resident Data";
            tc_Attributes.TabPages.Add(key, "Resident Data");
            tc_Attributes.TabPages[key].Controls.Add(residentDataLayout);
        }

        private void Populate(NonResidentDataAttributeContent nonResidentDataAttributeContent)
        {
            var nonResidentDataLayout = new NonResidentDataLayout();
            nonResidentDataLayout.Dock = DockStyle.Fill;
            nonResidentDataLayout.btn_Export.Click += btn_Export_Click;

            for (int i = 0; i < nonResidentDataAttributeContent.RunList.Length; i++)
            {
                var groupBox = new GroupBox();

                var lb_ClusterOffset = new Label();
                lb_ClusterOffset.Text = @"起始簇号";
                lb_ClusterOffset.Location = new Point(10, 30);
                lb_ClusterOffset.AutoSize = true;

                var tb_ClusterOffset = new TextBox();
                tb_ClusterOffset.Text = nonResidentDataAttributeContent.RunList[i].ClusterOffset.ToString();
                tb_ClusterOffset.Location = new Point(100, 27);

                var lb_ClusterLength = new Label();
                lb_ClusterLength.Text = @"所占用的簇数";
                lb_ClusterLength.Location = new Point(10, 60);
                lb_ClusterLength.AutoSize = true;

                var tb_ClusterLength = new TextBox();
                tb_ClusterLength.Text = nonResidentDataAttributeContent.RunList[i].ClusterCount.ToString();
                tb_ClusterLength.Location = new Point(100, 57);

                groupBox.Controls.Add(lb_ClusterOffset);
                groupBox.Controls.Add(tb_ClusterOffset);
                groupBox.Controls.Add(lb_ClusterLength);
                groupBox.Controls.Add(tb_ClusterLength);

                groupBox.Text = String.Format("DataRun {0}", i);
                groupBox.Location = new Point(30, 18 + 100 * i);
                groupBox.Size = new Size(210, 100);

                nonResidentDataLayout.Controls.Add(groupBox);
            }

            var key = "Non Resident Data";
            tc_Attributes.TabPages.Add(key, "Non Resident Data");
            tc_Attributes.TabPages[key].Controls.Add(nonResidentDataLayout);
        }

        private void btn_Export_Click(object sender, EventArgs e)
        {
            saveOutputFileDialog.FileName = fileRecord.Attributes.Select(a => a.AttributeContent)
                .OfType<FileNameAttributeContent>()
                .Select(ac => ac.FileName)
                .DefaultIfEmpty(String.Empty)
                .Last()
                .TrimStart('$');

            if (saveOutputFileDialog.ShowDialog() != DialogResult.OK) return;
            
            var nonResidentDataLayout = ((Control)sender).Parent as NonResidentDataLayout;
            if (nonResidentDataLayout == null) return;

            var groupBoxList = nonResidentDataLayout.Controls.OfType<GroupBox>().Where(gb => gb.Text.Contains("DataRun")).ToList();

            using (var stream = saveOutputFileDialog.OpenFile())
            using (var disk = DiskManager.GetStream())
            {
                foreach (var groupBox in groupBoxList)
                {
                    var textBoxList = groupBox.Controls.OfType<TextBox>().ToList();

                    var clusterOffset = long.Parse(textBoxList[0].Text);
                    var clusterCount = int.Parse(textBoxList[1].Text);

                    buffer = DiskManager.ReadSector(
                        disk,
                        (sectorsPrecedingPartition + clusterOffset * sectorsPerCluster) * DiskManager.SectorSize,
                        clusterCount * sectorsPerCluster);

                    //Rotate(buffer);
                    stream.Write(buffer, 0, buffer.Length);
                }
            }
        }

        private void PopulateFileRecordRawData(byte[] fileRecordRawData)
        {
            var tb_FileRecordFlatRawData = new TextBox();
            tb_FileRecordFlatRawData.Dock = DockStyle.Fill;
            tb_FileRecordFlatRawData.Multiline = true;
            tb_FileRecordFlatRawData.Text = fileRecordRawData.ToByteFlatString();

            var tb_FileRecordRawData = new TextBox();
            tb_FileRecordRawData.Dock = DockStyle.Fill;
            tb_FileRecordRawData.Multiline = true;
            tb_FileRecordRawData.Text = fileRecordRawData.ToByteString();

            var key1 = "Flat Raw Data";
            tc_Attributes.TabPages.Add(key1, "文件记录原始数据");
            tc_Attributes.TabPages[key1].Controls.Add(tb_FileRecordFlatRawData);

            var key2 = "Raw Data";
            tc_Attributes.TabPages.Add(key2, "文件记录原始数据");
            tc_Attributes.TabPages[key2].Controls.Add(tb_FileRecordRawData);
        }

        private async void btn_SearchFileName_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrWhiteSpace(tb_SearchFileName.Text)) 
                return;

            var searchFileName = tb_SearchFileName.Text;

            if (fileRecords == null)
            {
                using (var disk = DiskManager.GetStream())
                {
                    long offset = sectorIndex * DiskManager.SectorSize;
                    buffer = DiskManager.ReadSector(disk, offset, 2);
                }

                var MFT = FileRecordParser.Parse(buffer);

                var MFTStream = new List<byte>();
                {
                    var runList =
                        MFT.Attributes.Select(a => a.AttributeContent)
                           .OfType<NonResidentDataAttributeContent>()
                           .First()
                           .RunList;

                    foreach (var dataRun in runList)
                    {
                        long MFTSectorOffset = (sectorsPrecedingPartition + dataRun.ClusterOffset * sectorsPerCluster) * DiskManager.SectorSize;
                        int MFTSectorCount = dataRun.ClusterCount * sectorsPerCluster;

                        using (var disk = DiskManager.GetStream())
                        {
                            var MFTBlock = DiskManager.ReadSector(disk, MFTSectorOffset, MFTSectorCount);
                            MFTStream.AddRange(MFTBlock);
                        }
                    }

                    var MFTBuffer = MFTStream.ToArray();
                    var fileRecordCount = MFTBuffer.Length / fileRecordSize;
                    fileRecords = await ParseFileRecordsAsync(MFTBuffer, fileRecordSize, fileRecordCount);
                }
            }

            if (searchFileName != lastSearchFileName || lastSearchResults == null)
            {
                lastSearchFileName = searchFileName;
                lastSearchResults = fileRecords.Where(fr => fr.Attributes
                            .Select(a => a.AttributeContent)
                            .OfType<FileNameAttributeContent>()
                            .Any(ac => Regex.IsMatch(ac.FileName.ToLower(), WildcardToRegex(searchFileName.ToLower()))));

                currentResultIndex = 0;
            }

            var result = lastSearchResults.ElementAtOrDefault(currentResultIndex);

            if (result != null)
            {
                fileRecord = result;
                var index = fileRecords.IndexOf(result);
                nud_FileRecordIndex.Value = index;

                tb_SearchFileName.Text = searchFileName;
            }
        }

        private static async Task<List<FileRecord>> ParseFileRecordsAsync(byte[] buffer, int fileRecordSize, int fileRecordCount)
        {
            if (buffer.Length > fileRecordSize)
            {
                int firstlenth, secondLength;
                int firstFileRecordCount, secondFileRecordCount;

                if ((fileRecordCount % 2) == 0)
                {
                    firstFileRecordCount = secondFileRecordCount = fileRecordCount / 2;
                }
                else
                {
                    firstFileRecordCount = fileRecordCount / 2;
                    secondFileRecordCount = fileRecordCount / 2 + 1;
                }

                firstlenth = fileRecordSize * firstFileRecordCount;
                secondLength = fileRecordSize * secondFileRecordCount;

                var firstHalf = buffer.Take(firstlenth).ToArray();
                var secondHalf = buffer.Skip(firstlenth).Take(secondLength).ToArray();

                var firstTask = Task<List<FileRecord>>.Run(() => ParseFileRecordsAsync(firstHalf, fileRecordSize, firstFileRecordCount));
                var secondTask = Task<List<FileRecord>>.Run(() => ParseFileRecordsAsync(secondHalf, fileRecordSize, secondFileRecordCount));

                var firstFileRecords = await firstTask;
                var secondFileRecords = await secondTask;

                firstFileRecords.AddRange(secondFileRecords);
                return firstFileRecords;
            }
            else
            {
                var result = new List<FileRecord>();

                if (buffer.Any(b => b != 0))
                {
                    result.Add(FileRecordParser.Parse(buffer));
                }
                else
                {
                    var fileRecord = new FileRecord();
                    fileRecord.Attributes = new Model.FileRecordStructure.Attribute[0];
                    result.Add(fileRecord);
                }

                return result;
            }
        }

        private void btn_NextResult_Click(object sender, EventArgs e)
        {
            if (lastSearchResults == null) return;

            currentResultIndex++;

            if (currentResultIndex == lastSearchResults.Count())
            {
                currentResultIndex = 0;
            }

            var result = lastSearchResults.ElementAt(currentResultIndex);

            if (result != null)
            {
                fileRecord = result;
                var index = fileRecords.IndexOf(result);
                nud_FileRecordIndex.Value = index;

                tb_SearchFileName.Text = lastSearchFileName;
            }
        }

        /// <summary>
        /// Converts a wildcard to a regex.
        /// </summary>
        /// <param name="pattern">The wildcard pattern to convert.</param>
        /// <returns>A regex equivalent of the given wildcard.</returns>
        public static string WildcardToRegex(string pattern)
        {
            //. 为正则表达式的通配符，表示：与除 \n 之外的任何单个字符匹配。
            //* 为正则表达式的限定符，表示：匹配上一个元素零次或多次
            //? 为正则表达式的限定符，表示：匹配上一个元素零次或一次
            return "^" + Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + "$";
        }


    }
}
